package biz.yfsoft.app.fastframework.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import biz.yfsoft.app.fastframework.Constant;
import biz.yfsoft.app.fastframework.bo.Bo;
import biz.yfsoft.app.fastframework.core.hex.AuthErrorException;
import biz.yfsoft.app.fastframework.core.hex.EmailNotExistsException;
import biz.yfsoft.app.fastframework.core.hex.PasswordErrorException;
import biz.yfsoft.app.fastframework.core.hex.TokenInvalidException;
import biz.yfsoft.app.fastframework.core.hex.UserExistsException;
import biz.yfsoft.app.fastframework.core.hex.UserUnableException;
import biz.yfsoft.app.fastframework.kit.HttpKit;
import biz.yfsoft.app.fastframework.plugin.IFastPlugin;
import biz.yfsoft.app.fastframework.plugin.mail.Mailer;

import com.alibaba.fastjson.JSONObject;
import com.jfinal.aop.Before;
import com.jfinal.kit.EncryptionKit;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.tx.Tx;

/**
 * 系统的核心对象，包含一些核心的功能项；如：
 * 获取插件列表
 * 执行一个异步的任务
 * 触发一个webhook
 * 
 * 
 * @author YFsoft
 *
 */
public final class Fast {
	
	private Logger logger = LoggerFactory.getLogger(Fast.class);
	
	/*
	*获取自身的引用
	*/
	public static Fast me(){
		if(_fast == null){
			//logger.error("Fast Instance Havn't Been Init");
			System.out.println("Fast Instance Havn't Been Init");
		}
		return _fast;
	}
	
	//静态对象，单例模式的实现
	private static Fast _fast;
	
	//插件列表
	private Map<String,IFastPlugin> _pluginList;
	
	//行为列表
	private Map<String,Map<String,Object>> _behaviorMap;
	
	//将name取出做为key存放到Map中
	Map<String,Record> sysConfigMap = new HashMap<String,Record>();
	
	//运行时的配置
	Map<String,Object> _runtimeConfigMap = new HashMap<String,Object>();
	
	//开发模式
	private boolean _devMode = false;
	
	public void setRuntimeConfig(String key,Object val){
		_runtimeConfigMap.put(key, val);
	}
	
	public Object getRuntimeConfig(String key){
		return _runtimeConfigMap.get(key);
	}
	
	private void start(){
		_pluginList = new HashMap<String,IFastPlugin>();
		_behaviorMap = new HashMap<String,Map<String,Object>>();
		logger.warn("Fast Init Starting...");
		
		//获取开发模式
		_devMode = PropKit.getBoolean("devMode", false);
		
		//获取配置信息中的所有系统配置
		String sql = "select * from sys_setting where category = 'SYS' and status > 0";
		List<Record> sysConfigList = Db.find(sql);
		
		
		for(Record record : sysConfigList){
			sysConfigMap.put(record.getStr("name"), record);
		}
		
		
		//get the actived plugin list
		//获取被激活的插件列表
		List<Record> pluginList = Db.find("select * from sys_plugin where status > 0");
		if(pluginList!=null){
			for(Record rec : pluginList){
				String className = rec.getStr("class");
				String args = rec.getStr("args");
				String filepath = rec.getStr("filepath");
				Integer autoload = rec.getInt("autoload");
				IFastPlugin plugin = newPlugin(className,args,filepath);
				_pluginList.put(rec.getStr("name"), plugin);
				if(1==autoload)
					plugin.start();
			}
		}
		
		//get the behavior hook
		List<Record> list = Db.find("select * from sys_behavior where enable = 1 and status > 0");
		if(list!=null){
			for(Record rec : list){
				_behaviorMap.put(rec.getStr("name"), rec.getColumns());
			}
		}
	}
	
	/**
	 * 通过反射的方式，创建一个插件，并将运行参数或配置文件的地址传入到初始化函数中
	 * @param className 插件的全局限定名
	 * @param args	插件运行时参数，必须为json格式，优先使用该参数
	 * @param filepath	插件运行时的配置文件的路径，通常可能为一个绝对路径，也可能是classes目录下的配置文件
	 * @return
	 */
	public IFastPlugin newPlugin(String className,String args,String filepath){
		IFastPlugin plugin = null;
		try {
			plugin = (IFastPlugin)Class.forName(className).newInstance();
			//如果args参数不为空，则直接将该参数做为插件的配置项
			if(StrKit.notBlank(args)){
				//解析为json格式的数据
				JSONObject json = JSONObject.parseObject(args);
				plugin.init(json);
			}else{
				//如果args为空，且filepath不为空，则读取该配置文件，并做为插件的配置项
				if(StrKit.notBlank(filepath)){
					InputStream s = null;
					File f = new File(filepath);
					if(f.exists()){
						//该配置文件是一个磁盘文件
						try {
							s = new FileInputStream(f);
						} catch (FileNotFoundException e) {
						}
					}else{
						s = Fast.class.getResourceAsStream(filepath);
					}
					if(s != null){
						Properties properties = new Properties();
						try {
							properties.load(s);
							plugin.init(properties);
						} catch (IOException e) {
							//配置文件加载失败
						}finally{
							try {
								s.close();
							} catch (IOException e) {
							}
						}
					}
				}
			}
		} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
		return plugin;
	}
	
	public String getSysSetting(String key){
		if(sysConfigMap.containsKey(key)){
			return sysConfigMap.get(key).getStr("val");
		}
		return null;
	}
	
	//组件的初始化
	//主要是进行一次对象的创建，并调用 start() 函数进行组件的装载
	public static Fast init(){
		_fast = new Fast();
		_fast.start();
		return _fast;
	}
	
	public Map<String,IFastPlugin> getPluginList(){
		return _pluginList;
	}
	
	public boolean hasPlugin(String p){
		return _pluginList.containsKey(p);
	}
	public IFastPlugin getPlugin(String p){
		return _pluginList.get(p);
	}
	
	public void startPlugin(String p){
		if(!_pluginList.containsKey(p)) return;
		_pluginList.get(p).start();
	}
	
	public void stopPlugin(String p){
		if(!_pluginList.containsKey(p)) return;
		_pluginList.get(p).stop();
	}
	
	public Map<String,Object> getBehavior(String actionKey){
		return _behaviorMap.get(actionKey);
	}
	
	public boolean checkBehavior(String actionKey){
		return _behaviorMap.containsKey(actionKey);
	}
	
	public boolean devMode(){
		return _devMode ;
	}
	
	/**
	 * 授权登录的函数
	 * @param login_name 登录名称
	 * @param login_pass 登录密码
	 * @param ip 登录的ip
	 * @throws Exception 登录失败抛出的异常
	 */
	public void auth(String login_name,String login_pass,final String ip) 
			throws AuthErrorException{
		UsernamePasswordToken token = new UsernamePasswordToken();
		token.setUsername(login_name);
		token.setPassword(login_pass.toCharArray());
		Subject currentUser = SecurityUtils.getSubject();
		//通过shiro进行用户验证
		try{
			//没有验证用户状态
			currentUser.login(token);
		}catch(Exception hex){
			throw new AuthErrorException();
		}
	    Record user = Db.findFirst(
				"select * from sys_user where login_name = ? ",login_name);
	    //用户状态异常
	    if(user.getInt("status")<1){
	    	currentUser.logout();
	    	throw new UserUnableException();
	    }
	    //获取用户的角色信息
	    List<Record> roles = Db.find("select name,title from sys_role where id in (select roleid from sys_user_role where userid = ?)",user.get("id"));
		user.set("role", roles);
		
	    Session ss = currentUser.getSession();
	    ss.setTimeout(30*60*1000);
	    ss.setAttribute(Constant.CURRENT_USER, user);
	    
	    //Log session
		final String sessionid = ss.getId().toString();
		user.set("ip", ip);
		Fast.AynTask(new Runnable(){
			@Override
			public void run() {
				long now = System.currentTimeMillis();
				Bo bo = new Bo();
				bo.set("sessionid", sessionid);
				bo.set("uid", user.get("id"));
				bo.set("ip",ip);
				bo.set("logintime", now);
				bo.create();
				Db.save("sys_session", bo);
				//同时更新用户表中的信息
				Db.update("update sys_user set login_ip = ?,login_time = ? where id = ?",ip,now,user.get("id"));
			}
			
		});
	}
	
	/**
	 * 通过邮箱找回密码
	 * @param email 邮箱地址
	 * @return
	 * @exception 邮箱不存在的异常 
	 */
	public boolean findPassByEmail(String email)
		throws EmailNotExistsException{
		//1验证有效用户中是否存在该邮箱，不存在直接抛出异常
		long count = Db.queryLong("select count(*) from sys_user where status > 0 and email = ?",email);
		if(count!=1){
			throw new EmailNotExistsException();
		}
		//2生成一个找回密码的token，生成到用户表中
		//设定失效时间是72小时之后
		long now = System.currentTimeMillis();
		long then = now + (72 * 60 * 60 * 1000);
		String token = EncryptionKit.sha256Encrypt(""+now);
		Db.update("update sys_user set reset_pass_token = ?,reset_pass_valid_time = ? where email = ? ",token,then,email);
		
		//3通过模板对相应的邮件内容进行关键字的替换
		//TODO:使用模板来进行找回密码的修改
		//3.1通过全局配置获取到找回密码的URL链接
		String emailTemplate = "<p>您好 尊敬的会员！</p><p>您正在申请更改帐户密码，现在您可以点击以下链接来完成修改。</p><p><a href=\"{{url}}\">修改我的密码</a></p><p>如果您没有申请修改密码，请忽略这封邮件。</p><p>除非您点击以上链接并创建一个新的密码，否则您的密码不会改变。</p>";
		String resetPassUrl = this.getSysSetting("RESET_EMAIL_URL");
		//3.2通过模板找到找回密码的邮件模板，替换关键字
		resetPassUrl += token;
		//4发送找回密码的邮件
		HtmlEmail htmlEmail = Mailer.getHtmlEmail("【江阴海运煤炭交易市场】重置密码申请", email);
		try {
			emailTemplate = emailTemplate.replace("{{url}}",resetPassUrl);
			System.out.println(emailTemplate);
			htmlEmail.setHtmlMsg(emailTemplate);
			htmlEmail.send();
		} catch (EmailException e) {
			
		}
		return true;
	}
	
	/**
	 * 激活邮箱，根据邮箱对相应的邮件进行激活标识位的置1
	 * @param email
	 * @return true：ok ， false；
	 */
	public boolean activeEmail(String email){
		return 1 == Db.update("update sys_user set email_validate = 1 where email = ?", email);
	}
	
	/**
	 * 验证用户的该字段是否被人注册过
	 * @param field 字段名称，必须是在 sys_user表中存在的字段信息
	 * @param src 需要验证的用户字段名字
	 * @return
	 */
	public boolean checkExistsUserField(String field,String src){
		try{
			long count = Db.queryLong("select count(*) from sys_user where "+field+" = ?",src);
			if(count>0){
				return true;
			}
		}catch(Exception hex){
			
		}
		return false;
	}
	
	@Before(Tx.class)
	public void signup(String loginName,String email,int roleid,String loginPass)
			throws UserExistsException{
		//验证用户是否存在
		long count = Db.queryLong("select count(*) from sys_user where login_name = ? or email = ?",loginName,email);
		if(count>0){
			throw new UserExistsException();
		}
		//用户注册
		Bo user = new Bo();
		user.set("login_name", loginName);
		user.set("email",email);
		user.set("pass_encode", "MD5");
		user.set("login_pass", EncryptionKit.md5Encrypt(loginPass));
		user.set("is_admin", 0);
		user.create();
		Db.save("sys_user", user);
		
		Record role = new Record();
		
		role.set("roleid", roleid);
		role.set("userid", user.get("id"));
		Db.save("sys_user_role", role);
		
	}
	
	@Before(Tx.class)
	public void signup(String loginName,String email,int[] roleidArr,String loginPass)
			throws UserExistsException{
		//验证用户是否存在
		long count = Db.queryLong("select count(*) from sys_user where login_name = ? or email = ?",loginName,email);
		if(count>0){
			throw new UserExistsException();
		}
		//用户注册
		Bo user = new Bo();
		user.set("login_name", loginName);
		user.set("email",email);
		user.set("pass_encode", "MD5");
		user.set("login_pass", EncryptionKit.md5Encrypt(loginPass));
		user.set("is_admin", 0);
		user.create();
		Db.save("sys_user", user);
		
		for(int roleid:roleidArr){
			Record role = new Record();
			
			role.set("roleid", roleid);
			role.set("userid", user.get("id"));
			Db.save("sys_user_role", role);
		}
		
	}
	
	public void changePwd(String oldPass,String newPass)
			throws PasswordErrorException{
		Subject currentUser = SecurityUtils.getSubject();
		Session ss = currentUser.getSession();
		Record user = (Record)ss.getAttribute(Constant.CURRENT_USER);
		//获取该账户的密码加密方式
		String passEncode = user.getStr("pass_encode");
		//密码加密
		oldPass = EncryptionKit.encrypt(passEncode, oldPass);
		//判断旧密码是否正确
		boolean validate = user.getStr("login_pass").equals(oldPass);
		if(!validate){
			throw new PasswordErrorException();
		}
		newPass = EncryptionKit.encrypt(passEncode, newPass);
		Db.update("update sys_user set login_pass = ? ,updatetime = ? where id = ?", newPass,System.currentTimeMillis(),user.get("id"));
	}
	
	/**
	 * 通过重置密码的token进行密码的重置
	 * @param token 
	 * @param newPass 新密码
	 * @throws TokenInvalidException 重置的token失效的异常
	 */
	public void resetPass(String token,String newPass)
			throws TokenInvalidException{
		Record user = Db.findFirst("select * from sys_user where reset_pass_token = ? and status > 0 ",token);
		//未通过该token查找到该用户，则判定该token已经失效
		if(user == null){
			throw new TokenInvalidException();
		}
		//重置密码的失效已经过期
		if(user.getLong("reset_pass_valid_time")<System.currentTimeMillis()){
			throw new TokenInvalidException();
		}
		//获取该账户的密码加密方式
		String passEncode = user.getStr("pass_encode");
		//判断旧密码是否正确
		newPass = EncryptionKit.encrypt(passEncode, newPass);
		//更新密码，同时将 token 去掉
		Db.update("update sys_user set login_pass = ?,updatetime = ?,reset_pass_token = null,reset_pass_valid_time = 0 where id = ?", newPass,System.currentTimeMillis(),user.get("id"));
	}
	
	
	public void logout(){
		Subject currentUser = SecurityUtils.getSubject();
		Session ss = currentUser.getSession();
		final String sessionid = ss.getId().toString();
		currentUser.logout();
		//remove session
		Fast.AynTask(new Runnable(){
			@Override
			public void run() {
				Db.update("update sys_session set status= -1,logouttime = ? ,deleteflag = 1 where sessionid = ?",System.currentTimeMillis(),sessionid);
			}
			
		});
	}
	
	
	/**
	 * 执行一个异步的任务
	 * @param task 一个 runnable 接口的实现
	 */
	public static void AynTask(Runnable task){
		new Thread(task).start();
	}
	
	/**
	 * 设定一段时间之后执行一个异步任务
	 * @param task 一个 runnable 接口的任务
	 * @param after 多久之后
	 */
	public static void AynTask(final Runnable task,long after){
		new Timer().schedule(new TimerTask() {

			@Override
			public void run() {
				task.run();
			} 
			
		},after);
	}
	
	/**
	 * 触发一个webhook请求
	 * 通过http post方式发送请求
	 * 
	 * @param url webhook地址
	 * @param args 需要传递的参数集合
	 */
	public static void AynWebHook(final String url,final Map<String,String> args){
		(new Thread(){

			@Override
			public void run() {
				Bo bo = new Bo();
				bo.set("action","WEBHOOK");
				bo.set("ip","127.0.0.1");
				bo.set("result",1);
				bo.set("reason",url);
				bo.set("args",args==null?"":args.toString());
				bo.set("user",0);
				String result = null;
				try{
					result = HttpKit.http(url,args);
				}catch(Exception hex){
					result = hex.getMessage();
				}
				if(result.length()>2000){
					result = result.substring(0, 1999);
				}
				bo.set("remark",result);
				bo.create();
				Db.save("sys_log", bo);
			}
			
		}).start();
	}
	
	public static void main(String[] args){
		try{
			String result = HttpKit.post("http://fast.yfsoft.info/trade/main/buyers/payNotify",null,"");
			System.out.println(result);
		}catch(Exception hex){
			hex.printStackTrace();
		}
	}
}
